// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace System.Linq.Expressions
{
    /// <summary>
    /// Represents an expression that has a unary operator.
    /// </summary>
    [DebuggerTypeProxy(typeof(UnaryExpressionProxy))]
    public sealed class UnaryExpression : Expression
    {
        internal UnaryExpression(ExpressionType nodeType, Expression expression, Type type, MethodInfo? method)
        {
            Operand = expression;
            Method = method;
            NodeType = nodeType;
            Type = type;
        }

        /// <summary>
        /// Gets the static type of the expression that this <see cref="Expression"/> represents. (Inherited from <see cref="Expression"/>.)
        /// </summary>
        /// <returns>The <see cref="System.Type"/> that represents the static type of the expression.</returns>
        public sealed override Type Type { get; }

        /// <summary>
        /// Returns the node type of this <see cref="Expression"/>. (Inherited from <see cref="Expression"/>.)
        /// </summary>
        /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
        public sealed override ExpressionType NodeType { get; }

        /// <summary>
        /// Gets the operand of the unary operation.
        /// </summary>
        /// <returns> An <see cref="Expression"/> that represents the operand of the unary operation. Returns null if node type is <see cref="ExpressionType.Throw"/> with no operand.</returns>
        public Expression Operand { get; }

        /// <summary>
        /// Gets the implementing method for the unary operation.
        /// </summary>
        /// <returns>The <see cref="MethodInfo"/> that represents the implementing method.</returns>
        public MethodInfo? Method { get; }

        /// <summary>
        /// Gets a value that indicates whether the expression tree node represents a lifted call to an operator.
        /// </summary>
        /// <returns>true if the node represents a lifted call; otherwise, false.</returns>
        public bool IsLifted
        {
            get
            {
                if (NodeType == ExpressionType.TypeAs || NodeType == ExpressionType.Quote || NodeType == ExpressionType.Throw)
                {
                    return false;
                }
                bool operandIsNullable = Operand.Type.IsNullableType();
                bool resultIsNullable = this.Type.IsNullableType();
                if (Method != null)
                {
                    return (operandIsNullable && !TypeUtils.AreEquivalent(Method.GetParametersCached()[0].ParameterType, Operand.Type)) ||
                           (resultIsNullable && !TypeUtils.AreEquivalent(Method.ReturnType, this.Type));
                }
                return operandIsNullable || resultIsNullable;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the expression tree node represents a lifted call to an operator whose return type is lifted to a nullable type.
        /// </summary>
        /// <returns>true if the operator's return type is lifted to a nullable type; otherwise, false.</returns>
        public bool IsLiftedToNull => IsLifted && this.Type.IsNullableType();

        /// <summary>
        /// Dispatches to the specific visit method for this node type.
        /// </summary>
        protected internal override Expression Accept(ExpressionVisitor visitor)
        {
            return visitor.VisitUnary(this);
        }

        /// <summary>
        /// Gets a value that indicates whether the expression tree node can be reduced.
        /// </summary>
        public override bool CanReduce
        {
            get
            {
                switch (NodeType)
                {
                    case ExpressionType.PreIncrementAssign:
                    case ExpressionType.PreDecrementAssign:
                    case ExpressionType.PostIncrementAssign:
                    case ExpressionType.PostDecrementAssign:
                        return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Reduces the expression node to a simpler expression.
        /// If CanReduce returns true, this should return a valid expression.
        /// This method is allowed to return another node which itself
        /// must be reduced.
        /// </summary>
        /// <returns>The reduced expression.</returns>
        public override Expression Reduce()
        {
            if (CanReduce)
            {
                switch (Operand.NodeType)
                {
                    case ExpressionType.Index:
                        return ReduceIndex();
                    case ExpressionType.MemberAccess:
                        return ReduceMember();
                    default:
                        Debug.Assert(Operand.NodeType == ExpressionType.Parameter);
                        return ReduceVariable();
                }
            }
            return this;
        }

        private bool IsPrefix
        {
            get { return NodeType == ExpressionType.PreIncrementAssign || NodeType == ExpressionType.PreDecrementAssign; }
        }

        private UnaryExpression FunctionalOp(Expression operand)
        {
            ExpressionType functional;
            if (NodeType == ExpressionType.PreIncrementAssign || NodeType == ExpressionType.PostIncrementAssign)
            {
                functional = ExpressionType.Increment;
            }
            else
            {
                Debug.Assert(NodeType == ExpressionType.PreDecrementAssign || NodeType == ExpressionType.PostDecrementAssign);
                functional = ExpressionType.Decrement;
            }
            return new UnaryExpression(functional, operand, operand.Type, Method);
        }

        private Expression ReduceVariable()
        {
            if (IsPrefix)
            {
                // (op) var
                // ... is reduced into ...
                // var = op(var)
                return Assign(Operand, FunctionalOp(Operand));
            }
            // var (op)
            // ... is reduced into ...
            // temp = var
            // var = op(var)
            // temp
            ParameterExpression temp = Parameter(Operand.Type, name: null);
            return Block(
                new TrueReadOnlyCollection<ParameterExpression>(temp),
                new TrueReadOnlyCollection<Expression>(
                    Assign(temp, Operand),
                    Assign(Operand, FunctionalOp(temp)),
                    temp
                )
            );
        }

        private Expression ReduceMember()
        {
            var member = (MemberExpression)Operand;
            if (member.Expression == null)
            {
                //static member, reduce the same as variable
                return ReduceVariable();
            }
            else
            {
                ParameterExpression temp1 = Parameter(member.Expression.Type, name: null);
                BinaryExpression initTemp1 = Assign(temp1, member.Expression);
                member = MakeMemberAccess(temp1, member.Member);

                if (IsPrefix)
                {
                    // (op) value.member
                    // ... is reduced into ...
                    // temp1 = value
                    // temp1.member = op(temp1.member)
                    return Block(
                        new TrueReadOnlyCollection<ParameterExpression>(temp1),
                        new TrueReadOnlyCollection<Expression>(
                            initTemp1,
                            Assign(member, FunctionalOp(member))
                        )
                    );
                }

                // value.member (op)
                // ... is reduced into ...
                // temp1 = value
                // temp2 = temp1.member
                // temp1.member = op(temp2)
                // temp2
                ParameterExpression temp2 = Parameter(member.Type, name: null);
                return Block(
                    new TrueReadOnlyCollection<ParameterExpression>(temp1, temp2),
                    new TrueReadOnlyCollection<Expression>(
                        initTemp1,
                        Assign(temp2, member),
                        Assign(member, FunctionalOp(temp2)),
                        temp2
                    )
                );
            }
        }

        private Expression ReduceIndex()
        {
            // left[a0, a1, ... aN] (op)
            //
            // ... is reduced into ...
            //
            // tempObj = left
            // tempArg0 = a0
            // ...
            // tempArgN = aN
            // tempValue = tempObj[tempArg0, ... tempArgN]
            // tempObj[tempArg0, ... tempArgN] = op(tempValue)
            // tempValue

            bool prefix = IsPrefix;
            var index = (IndexExpression)Operand;
            int count = index.ArgumentCount;
            var block = new Expression[count + (prefix ? 2 : 4)];
            var temps = new ParameterExpression[count + (prefix ? 1 : 2)];
            var args = new ParameterExpression[count];

            int i = 0;
            temps[i] = Parameter(index.Object!.Type, name: null);
            block[i] = Assign(temps[i], index.Object);
            i++;
            while (i <= count)
            {
                Expression arg = index.GetArgument(i - 1);
                args[i - 1] = temps[i] = Parameter(arg.Type, name: null);
                block[i] = Assign(temps[i], arg);
                i++;
            }
            index = MakeIndex(temps[0], index.Indexer, new TrueReadOnlyCollection<Expression>(args));
            if (!prefix)
            {
                ParameterExpression lastTemp = temps[i] = Parameter(index.Type, name: null);
                block[i] = Assign(temps[i], index);
                i++;
                Debug.Assert(i == temps.Length);
                block[i++] = Assign(index, FunctionalOp(lastTemp));
                block[i++] = lastTemp;
            }
            else
            {
                Debug.Assert(i == temps.Length);
                block[i++] = Assign(index, FunctionalOp(index));
            }
            Debug.Assert(i == block.Length);
            return Block(new TrueReadOnlyCollection<ParameterExpression>(temps), new TrueReadOnlyCollection<Expression>(block));
        }

        /// <summary>
        /// Creates a new expression that is like this one, but using the
        /// supplied children. If all of the children are the same, it will
        /// return this expression.
        /// </summary>
        /// <param name="operand">The <see cref="Operand"/> property of the result.</param>
        /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
        public UnaryExpression Update(Expression operand)
        {
            if (operand == Operand)
            {
                return this;
            }
            return Expression.MakeUnary(NodeType, operand, Type, Method);
        }
    }

    public partial class Expression
    {
        /// <summary>
        /// Creates a <see cref="UnaryExpression"/>, given an operand, by calling the appropriate factory method.
        /// </summary>
        /// <param name="unaryType">The <see cref="ExpressionType"/> that specifies the type of unary operation.</param>
        /// <param name="operand">An <see cref="Expression"/> that represents the operand.</param>
        /// <param name="type">The <see cref="Type"/> that specifies the type to be converted to (pass null if not applicable).</param>
        /// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
        /// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
        public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type)
        {
            return MakeUnary(unaryType, operand, type, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/>, given an operand and implementing method, by calling the appropriate factory method.
        /// </summary>
        /// <param name="unaryType">The <see cref="ExpressionType"/> that specifies the type of unary operation.</param>
        /// <param name="operand">An <see cref="Expression"/> that represents the operand.</param>
        /// <param name="type">The <see cref="Type"/> that specifies the type to be converted to (pass null if not applicable).</param>
        /// <param name="method">The <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
        /// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
        public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type, MethodInfo? method) =>
            unaryType switch
            {
                ExpressionType.Negate => Negate(operand, method),
                ExpressionType.NegateChecked => NegateChecked(operand, method),
                ExpressionType.Not => Not(operand, method),
                ExpressionType.IsFalse => IsFalse(operand, method),
                ExpressionType.IsTrue => IsTrue(operand, method),
                ExpressionType.OnesComplement => OnesComplement(operand, method),
                ExpressionType.ArrayLength => ArrayLength(operand),
                ExpressionType.Convert => Convert(operand, type, method),
                ExpressionType.ConvertChecked => ConvertChecked(operand, type, method),
                ExpressionType.Throw => Throw(operand, type),
                ExpressionType.TypeAs => TypeAs(operand, type),
                ExpressionType.Quote => Quote(operand),
                ExpressionType.UnaryPlus => UnaryPlus(operand, method),
                ExpressionType.Unbox => Unbox(operand, type),
                ExpressionType.Increment => Increment(operand, method),
                ExpressionType.Decrement => Decrement(operand, method),
                ExpressionType.PreIncrementAssign => PreIncrementAssign(operand, method),
                ExpressionType.PostIncrementAssign => PostIncrementAssign(operand, method),
                ExpressionType.PreDecrementAssign => PreDecrementAssign(operand, method),
                ExpressionType.PostDecrementAssign => PostDecrementAssign(operand, method),
                _ => throw Error.UnhandledUnary(unaryType, nameof(unaryType)),
            };

        private static UnaryExpression GetUserDefinedUnaryOperatorOrThrow(ExpressionType unaryType, string name, Expression operand)
        {
            UnaryExpression? u = GetUserDefinedUnaryOperator(unaryType, name, operand);
            if (u != null)
            {
                ValidateParamsWithOperandsOrThrow(u.Method!.GetParametersCached()[0].ParameterType, operand.Type, unaryType, name);
                return u;
            }
            throw Error.UnaryOperatorNotDefined(unaryType, operand.Type);
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
            Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/pull/2125.")]
        private static UnaryExpression? GetUserDefinedUnaryOperator(ExpressionType unaryType, string name, Expression operand)
        {
            Type operandType = operand.Type;
            Type[] types = new Type[] { operandType };
            Type nnOperandType = operandType.GetNonNullableType();
            MethodInfo? method = nnOperandType.GetAnyStaticMethodValidated(name, types);
            if (method != null)
            {
                return new UnaryExpression(unaryType, operand, method.ReturnType, method);
            }
            // try lifted call
            if (operandType.IsNullableType())
            {
                types[0] = nnOperandType;
                method = nnOperandType.GetAnyStaticMethodValidated(name, types);
                if (method != null && method.ReturnType.IsValueType && !method.ReturnType.IsNullableType())
                {
                    return new UnaryExpression(unaryType, operand, method.ReturnType.LiftPrimitiveOrThrow(), method);
                }
            }
            return null;
        }

        private static UnaryExpression GetMethodBasedUnaryOperator(ExpressionType unaryType, Expression operand, MethodInfo method)
        {
            Debug.Assert(method != null);
            ValidateOperator(method);
            ParameterInfo[] pms = method.GetParametersCached();
            if (pms.Length != 1)
                throw Error.IncorrectNumberOfMethodCallArguments(method, nameof(method));
            if (ParameterIsAssignable(pms[0], operand.Type))
            {
                ValidateParamsWithOperandsOrThrow(pms[0].ParameterType, operand.Type, unaryType, method.Name);
                return new UnaryExpression(unaryType, operand, method.ReturnType, method);
            }
            // check for lifted call
            if (operand.Type.IsNullableType() &&
                ParameterIsAssignable(pms[0], operand.Type.GetNonNullableType()) &&
                method.ReturnType.IsValueType && !method.ReturnType.IsNullableType())
            {
                return new UnaryExpression(unaryType, operand, method.ReturnType.LiftPrimitiveOrThrow(), method);
            }

            throw Error.OperandTypesDoNotMatchParameters(unaryType, method.Name);
        }

        private static UnaryExpression GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
        {
            UnaryExpression? u = GetUserDefinedCoercion(coercionType, expression, convertToType);
            if (u != null)
            {
                return u;
            }
            throw Error.CoercionOperatorNotDefined(expression.Type, convertToType);
        }

        private static UnaryExpression? GetUserDefinedCoercion(ExpressionType coercionType, Expression expression, Type convertToType)
        {
            MethodInfo? method = TypeUtils.GetUserDefinedCoercionMethod(expression.Type, convertToType);
            if (method != null)
            {
                return new UnaryExpression(coercionType, expression, convertToType, method);
            }
            else
            {
                return null;
            }
        }

        private static UnaryExpression GetMethodBasedCoercionOperator(ExpressionType unaryType, Expression operand, Type convertToType, MethodInfo method)
        {
            Debug.Assert(method != null);
            ValidateOperator(method);
            ParameterInfo[] pms = method.GetParametersCached();
            if (pms.Length != 1)
            {
                throw Error.IncorrectNumberOfMethodCallArguments(method, nameof(method));
            }
            if (ParameterIsAssignable(pms[0], operand.Type) && TypeUtils.AreEquivalent(method.ReturnType, convertToType))
            {
                return new UnaryExpression(unaryType, operand, method.ReturnType, method);
            }
            // check for lifted call
            if ((operand.Type.IsNullableType() || convertToType.IsNullableType()) &&
                ParameterIsAssignable(pms[0], operand.Type.GetNonNullableType()) &&
                (TypeUtils.AreEquivalent(method.ReturnType, convertToType.GetNonNullableType()) ||
                TypeUtils.AreEquivalent(method.ReturnType, convertToType)))
            {
                return new UnaryExpression(unaryType, operand, convertToType, method);
            }
            throw Error.OperandTypesDoNotMatchParameters(unaryType, method.Name);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents an arithmetic negation operation.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Negate"/> and the <see cref="UnaryExpression.Operand"/> properties set to the specified value.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="expression"/> is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown when the unary minus operator is not defined for <paramref name="expression"/>.Type.</exception>
        public static UnaryExpression Negate(Expression expression)
        {
            return Negate(expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents an arithmetic negation operation.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Negate"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="UnaryExpression.Method"/> properties set to the specified value.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="InvalidOperationException">Thrown when <paramref name="method"/> is null and the unary minus operator is not defined for <paramref name="expression"/>.Type (or its corresponding non-nullable type if it is a nullable value type) is not assignable to the argument type of the method represented by method.</exception>
        public static UnaryExpression Negate(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsArithmetic() && !expression.Type.IsUnsignedInt())
                {
                    return new UnaryExpression(ExpressionType.Negate, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.Negate, "op_UnaryNegation", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.Negate, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a unary plus operation.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.UnaryPlus"/> and the <see cref="UnaryExpression.Operand"/> property set to the specified value.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="expression"/> is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown when the unary minus operator is not defined for <paramref name="expression"/>.Type.</exception>
        public static UnaryExpression UnaryPlus(Expression expression)
        {
            return UnaryPlus(expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a unary plus operation.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.UnaryPlus"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="UnaryExpression.Method"/>property set to the specified value.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="InvalidOperationException">Thrown when <paramref name="method"/> is null and the unary minus operator is not defined for <paramref name="expression"/>.Type (or its corresponding non-nullable type if it is a nullable value type) is not assignable to the argument type of the method represented by method.</exception>
        public static UnaryExpression UnaryPlus(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsArithmetic())
                {
                    return new UnaryExpression(ExpressionType.UnaryPlus, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.UnaryPlus, "op_UnaryPlus", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.UnaryPlus, expression, method);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents an arithmetic negation operation that has overflow checking.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.NegateChecked"/> and the <see cref="UnaryExpression.Operand"/> property set to the specified value.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="expression"/> is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown when the unary minus operator is not defined for <paramref name="expression"/>.Type.</exception>
        public static UnaryExpression NegateChecked(Expression expression)
        {
            return NegateChecked(expression, method: null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents an arithmetic negation operation that has overflow checking. The implementing method can be specified.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.NegateChecked"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="UnaryExpression.Method"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="InvalidOperationException">
        /// <paramref name="method"/> is null and the unary minus operator is not defined for <paramref name="expression"/>.Type.-or-<paramref name="expression"/>.Type (or its corresponding non-nullable type if it is a nullable value type) is not assignable to the argument type of the method represented by <paramref name="method"/>.</exception>
        public static UnaryExpression NegateChecked(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsArithmetic() && !expression.Type.IsUnsignedInt())
                {
                    return new UnaryExpression(ExpressionType.NegateChecked, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.NegateChecked, "op_UnaryNegation", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.NegateChecked, expression, method);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a bitwise complement operation.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Not"/> and the <see cref="UnaryExpression.Operand"/> property set to the specified value.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> is null.</exception>
        /// <exception cref="InvalidOperationException">The unary not operator is not defined for <paramref name="expression"/>.Type.</exception>
        public static UnaryExpression Not(Expression expression)
        {
            return Not(expression, method: null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a bitwise complement operation. The implementing method can be specified.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Not"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="UnaryExpression.Method"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="InvalidOperationException">
        /// <paramref name="method"/> is null and the unary not operator is not defined for <paramref name="expression"/>.Type.-or-<paramref name="expression"/>.Type (or its corresponding non-nullable type if it is a nullable value type) is not assignable to the argument type of the method represented by <paramref name="method"/>.</exception>
        public static UnaryExpression Not(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsIntegerOrBool())
                {
                    return new UnaryExpression(ExpressionType.Not, expression, expression.Type, null);
                }
                UnaryExpression? u = GetUserDefinedUnaryOperator(ExpressionType.Not, "op_LogicalNot", expression);
                if (u != null)
                {
                    return u;
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.Not, "op_OnesComplement", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.Not, expression, method);
        }

        /// <summary>
        /// Returns whether the expression evaluates to false.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to evaluate.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression IsFalse(Expression expression)
        {
            return IsFalse(expression, method: null);
        }

        /// <summary>
        /// Returns whether the expression evaluates to false.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to evaluate.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression IsFalse(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsBool())
                {
                    return new UnaryExpression(ExpressionType.IsFalse, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.IsFalse, "op_False", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.IsFalse, expression, method);
        }

        /// <summary>
        /// Returns whether the expression evaluates to true.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to evaluate.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression IsTrue(Expression expression)
        {
            return IsTrue(expression, method: null);
        }

        /// <summary>
        /// Returns whether the expression evaluates to true.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to evaluate.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression IsTrue(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsBool())
                {
                    return new UnaryExpression(ExpressionType.IsTrue, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.IsTrue, "op_True", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.IsTrue, expression, method);
        }

        /// <summary>
        /// Returns the expression representing the ones complement.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/>.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression OnesComplement(Expression expression)
        {
            return OnesComplement(expression, method: null);
        }

        /// <summary>
        /// Returns the expression representing the ones complement.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/>.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression OnesComplement(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsInteger())
                {
                    return new UnaryExpression(ExpressionType.OnesComplement, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.OnesComplement, "op_OnesComplement", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.OnesComplement, expression, method);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents an explicit reference or boxing conversion where null is supplied if the conversion fails.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.TypeAs"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="Expression.Type"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="type">A <see cref="System.Type"/> to set the <see cref="Type"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
        public static UnaryExpression TypeAs(Expression expression, Type type)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            ArgumentNullException.ThrowIfNull(type);
            TypeUtils.ValidateType(type, nameof(type));
            if (type.IsValueType && !type.IsNullableType())
            {
                throw Error.IncorrectTypeForTypeAs(type, nameof(type));
            }

            return new UnaryExpression(ExpressionType.TypeAs, expression, type, null);
        }

        /// <summary>
        /// <summary>Creates a <see cref="UnaryExpression"/> that represents an explicit unboxing.</summary>
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to unbox.</param>
        /// <param name="type">The new <see cref="System.Type"/> of the expression.</param>
        /// <returns>An instance of <see cref="UnaryExpression"/>.</returns>
        public static UnaryExpression Unbox(Expression expression, Type type)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            ArgumentNullException.ThrowIfNull(type);
            if (!expression.Type.IsInterface && expression.Type != typeof(object))
            {
                throw Error.InvalidUnboxType(nameof(expression));
            }
            if (!type.IsValueType) throw Error.InvalidUnboxType(nameof(type));
            TypeUtils.ValidateType(type, nameof(type));
            return new UnaryExpression(ExpressionType.Unbox, expression, type, null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a conversion operation.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Convert"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="Expression.Type"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="type">A <see cref="System.Type"/> to set the <see cref="Type"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
        /// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.</exception>
        public static UnaryExpression Convert(Expression expression, Type type)
        {
            return Convert(expression, type, method: null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a conversion operation for which the implementing method is specified.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Convert"/> and the <see cref="UnaryExpression.Operand"/>, <see cref="Expression.Type"/>, and <see cref="UnaryExpression.Method"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="type">A <see cref="System.Type"/> to set the <see cref="Type"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
        /// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.-or-<paramref name="expression"/>.Type is not assignable to the argument type of the method represented by <paramref name="method"/>.-or-The return type of the method represented by <paramref name="method"/> is not assignable to <paramref name="type"/>.-or-<paramref name="expression"/>.Type or <paramref name="type"/> is a nullable value type and the corresponding non-nullable value type does not equal the argument type or the return type, respectively, of the method represented by <paramref name="method"/>.</exception>
        public static UnaryExpression Convert(Expression expression, Type type, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            ArgumentNullException.ThrowIfNull(type);
            TypeUtils.ValidateType(type, nameof(type));
            if (method == null)
            {
                if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type) ||
                    expression.Type.HasReferenceConversionTo(type))
                {
                    return new UnaryExpression(ExpressionType.Convert, expression, type, null);
                }
                return GetUserDefinedCoercionOrThrow(ExpressionType.Convert, expression, type);
            }
            return GetMethodBasedCoercionOperator(ExpressionType.Convert, expression, type, method);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a conversion operation that throws an exception if the target type is overflowed.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.ConvertChecked"/> and the <see cref="UnaryExpression.Operand"/> and <see cref="Expression.Type"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="type">A <see cref="System.Type"/> to set the <see cref="Type"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
        /// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.</exception>
        public static UnaryExpression ConvertChecked(Expression expression, Type type)
        {
            return ConvertChecked(expression, type, method: null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents a conversion operation that throws an exception if the target type is overflowed and for which the implementing method is specified.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.ConvertChecked"/> and the <see cref="UnaryExpression.Operand"/>, <see cref="Expression.Type"/>, and <see cref="UnaryExpression.Method"/> properties set to the specified values.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <param name="type">A <see cref="System.Type"/> to set the <see cref="Type"/> property equal to.</param>
        /// <param name="method">A <see cref="MethodInfo"/> to set the <see cref="UnaryExpression.Method"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
        /// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
        /// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.-or-<paramref name="expression"/>.Type is not assignable to the argument type of the method represented by <paramref name="method"/>.-or-The return type of the method represented by <paramref name="method"/> is not assignable to <paramref name="type"/>.-or-<paramref name="expression"/>.Type or <paramref name="type"/> is a nullable value type and the corresponding non-nullable value type does not equal the argument type or the return type, respectively, of the method represented by <paramref name="method"/>.</exception>
        public static UnaryExpression ConvertChecked(Expression expression, Type type, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            ArgumentNullException.ThrowIfNull(type);
            TypeUtils.ValidateType(type, nameof(type));
            if (method == null)
            {
                if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type))
                {
                    return new UnaryExpression(ExpressionType.ConvertChecked, expression, type, null);
                }
                if (expression.Type.HasReferenceConversionTo(type))
                {
                    return new UnaryExpression(ExpressionType.Convert, expression, type, null);
                }
                return GetUserDefinedCoercionOrThrow(ExpressionType.ConvertChecked, expression, type);
            }
            return GetMethodBasedCoercionOperator(ExpressionType.ConvertChecked, expression, type, method);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents getting the length of a one-dimensional, zero-based array.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.ArrayLength"/> and the <see cref="UnaryExpression.Operand"/> property equal to <paramref name="array"/>.</returns>
        /// <param name="array">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="array"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="array"/>.Type does not represent a single-dimensional, zero-based array type.</exception>
        public static UnaryExpression ArrayLength(Expression array)
        {
            ExpressionUtils.RequiresCanRead(array, nameof(array));
            if (!array.Type.IsSZArray)
            {
                if (!array.Type.IsArray || !typeof(Array).IsAssignableFrom(array.Type))
                {
                    throw Error.ArgumentMustBeArray(nameof(array));
                }

                throw Error.ArgumentMustBeSingleDimensionalArrayType(nameof(array));
            }

            return new UnaryExpression(ExpressionType.ArrayLength, array, typeof(int), null);
        }

        /// <summary>Creates a <see cref="UnaryExpression"/> that represents an expression that has a constant value of type <see cref="Expression"/>.</summary>
        /// <returns>A <see cref="UnaryExpression"/> that has the <see cref="NodeType"/> property equal to <see cref="ExpressionType.Quote"/> and the <see cref="UnaryExpression.Operand"/> property set to the specified value.</returns>
        /// <param name="expression">An <see cref="Expression"/> to set the <see cref="UnaryExpression.Operand"/> property equal to.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="expression"/> is null.</exception>
        public static UnaryExpression Quote(Expression expression)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            LambdaExpression? lambda = expression as LambdaExpression;
            if (lambda == null)
            {
                throw Error.QuotedExpressionMustBeLambda(nameof(expression));
            }

            return new UnaryExpression(ExpressionType.Quote, lambda, lambda.PublicType, null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a rethrowing of an exception.
        /// </summary>
        /// <returns>A <see cref="UnaryExpression"/> that represents a rethrowing of an exception.</returns>
        public static UnaryExpression Rethrow()
        {
            return Throw(value: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a rethrowing of an exception with a given type.
        /// </summary>
        /// <param name="type">The new <see cref="Type"/> of the expression.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents a rethrowing of an exception.</returns>
        public static UnaryExpression Rethrow(Type type)
        {
            return Throw(null, type);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a throwing of an exception.
        /// </summary>
        /// <param name="value">An <see cref="Expression"/>.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the exception.</returns>
        public static UnaryExpression Throw(Expression? value)
        {
            return Throw(value, typeof(void));
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents a throwing of a value with a given type.
        /// </summary>
        /// <param name="value">An <see cref="Expression"/>.</param>
        /// <param name="type">The new <see cref="Type"/> of the expression.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the exception.</returns>
        public static UnaryExpression Throw(Expression? value, Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
            TypeUtils.ValidateType(type, nameof(type));
            if (value != null)
            {
                ExpressionUtils.RequiresCanRead(value, nameof(value));
                if (value.Type.IsValueType) throw Error.ArgumentMustNotHaveValueType(nameof(value));
            }
            return new UnaryExpression(ExpressionType.Throw, value!, type, null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the incrementing of the expression by 1.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to increment.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the incremented expression.</returns>
        public static UnaryExpression Increment(Expression expression)
        {
            return Increment(expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the incrementing of the expression by 1.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to increment.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the incremented expression.</returns>
        public static UnaryExpression Increment(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsArithmetic())
                {
                    return new UnaryExpression(ExpressionType.Increment, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.Increment, "op_Increment", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.Increment, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the decrementing of the expression by 1.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to decrement.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the decremented expression.</returns>
        public static UnaryExpression Decrement(Expression expression)
        {
            return Decrement(expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the decrementing of the expression by 1.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to decrement.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the decremented expression.</returns>
        public static UnaryExpression Decrement(Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            if (method == null)
            {
                if (expression.Type.IsArithmetic())
                {
                    return new UnaryExpression(ExpressionType.Decrement, expression, expression.Type, null);
                }
                return GetUserDefinedUnaryOperatorOrThrow(ExpressionType.Decrement, "op_Decrement", expression);
            }
            return GetMethodBasedUnaryOperator(ExpressionType.Decrement, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that increments the expression by 1
        /// and assigns the result back to the expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PreIncrementAssign(Expression expression)
        {
            return MakeOpAssignUnary(ExpressionType.PreIncrementAssign, expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that increments the expression by 1
        /// and assigns the result back to the expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PreIncrementAssign(Expression expression, MethodInfo? method)
        {
            return MakeOpAssignUnary(ExpressionType.PreIncrementAssign, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that decrements the expression by 1
        /// and assigns the result back to the expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PreDecrementAssign(Expression expression)
        {
            return MakeOpAssignUnary(ExpressionType.PreDecrementAssign, expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that decrements the expression by 1
        /// and assigns the result back to the expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PreDecrementAssign(Expression expression, MethodInfo? method)
        {
            return MakeOpAssignUnary(ExpressionType.PreDecrementAssign, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the assignment of the expression
        /// followed by a subsequent increment by 1 of the original expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PostIncrementAssign(Expression expression)
        {
            return MakeOpAssignUnary(ExpressionType.PostIncrementAssign, expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the assignment of the expression
        /// followed by a subsequent increment by 1 of the original expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PostIncrementAssign(Expression expression, MethodInfo? method)
        {
            return MakeOpAssignUnary(ExpressionType.PostIncrementAssign, expression, method);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the assignment of the expression
        /// followed by a subsequent decrement by 1 of the original expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PostDecrementAssign(Expression expression)
        {
            return MakeOpAssignUnary(ExpressionType.PostDecrementAssign, expression, method: null);
        }

        /// <summary>
        /// Creates a <see cref="UnaryExpression"/> that represents the assignment of the expression
        /// followed by a subsequent decrement by 1 of the original expression.
        /// </summary>
        /// <param name="expression">An <see cref="Expression"/> to apply the operations on.</param>
        /// <param name="method">A <see cref="MethodInfo"/> that represents the implementing method.</param>
        /// <returns>A <see cref="UnaryExpression"/> that represents the resultant expression.</returns>
        public static UnaryExpression PostDecrementAssign(Expression expression, MethodInfo? method)
        {
            return MakeOpAssignUnary(ExpressionType.PostDecrementAssign, expression, method);
        }

        private static UnaryExpression MakeOpAssignUnary(ExpressionType kind, Expression expression, MethodInfo? method)
        {
            ExpressionUtils.RequiresCanRead(expression, nameof(expression));
            RequiresCanWrite(expression, nameof(expression));

            UnaryExpression result;
            if (method == null)
            {
                if (expression.Type.IsArithmetic())
                {
                    return new UnaryExpression(kind, expression, expression.Type, null);
                }
                string name;
                if (kind == ExpressionType.PreIncrementAssign || kind == ExpressionType.PostIncrementAssign)
                {
                    name = "op_Increment";
                }
                else
                {
                    Debug.Assert(kind == ExpressionType.PreDecrementAssign || kind == ExpressionType.PostDecrementAssign);
                    name = "op_Decrement";
                }
                result = GetUserDefinedUnaryOperatorOrThrow(kind, name, expression);
            }
            else
            {
                result = GetMethodBasedUnaryOperator(kind, expression, method);
            }
            // return type must be assignable back to the operand type
            if (!TypeUtils.AreReferenceAssignable(expression.Type, result.Type))
            {
                throw Error.UserDefinedOpMustHaveValidReturnType(kind, method!.Name);
            }
            return result;
        }
    }
}
